﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;

public class FacePainter : MonoBehaviour
{
    public SkinnedMeshRenderer smr;

    public Texture[] brushTex;
    public Texture2D faceTex;
    public Texture2D hairTex;
    public Texture2D matCapTex;

    public float brushSize;
    public Vector3 brushSizeMOM;
    public LabelSlider brushSizeSlider;

    public float brushStrength;
    public Vector3 brushStrengthMOM;
    public LabelSlider brushStrengthSlider;

    public int brushType;
    public Vector3 brushTypeMOM;
    public LabelSlider brushTypeSlider;
    public Sprite[] brushTypePreviewSprites;
    public Image brushTypePreviewImage;

    public Button openTextureButton;
    public Button openHairTextureButton;
    public Button saveTextureButton;

    public bool isOn;
    public List<Material> mats;// 0 for hairUnitPrefabSmr, 1 for skin(paint), >=2 for other face feature
    public const int hair0 = 0;
    public const int skin = 1;

    public Dropdown portraitDrop;
    public Dropdown faceTexDrop;
    public Dropdown hairTexDrop;
    public Dropdown matCapTexDrop;
    public string portraitFolder = "portrait/";
    public string faceTexFolder = "face/";
    public string hairTexFolder = "hair/";
    public string matCapTexFolder = "matCap/";
    public string portraitDirPath;
    public string faceDirPath;
    public string hairDirPath;
    public string matCapDirPath;
    public string portraitFilePath;
    public string faceFilePath;
    public string hairFilePath;
    public string matCapFilePath;
    public Color brushColor;
    public ColorPickerSlider brushColorSlider;
    public Toggle toggle;

    public Toggle paintToEyeWhiteToggle;
    public bool paintToEyeWhiteOn;

    private HairUnitEmitter emitter;
    public string baseTextureName;
    public string darkTextureName;
    public string dark2TextureName;
    public string matCapTextureName = "_MatCap_Sampler";

    public int textureSqrtSize;
    bool ignoreChange = false;

    public string defaultFileName = "默认";
    public Toggle facePanelToggle;
    public Toggle paintPanelToggle;
    void OnPaintToEyeWhiteToggleChanged(bool v)
    {
        paintToEyeWhiteOn = v;
    }
    void OnPortraitDropChanged(int v)
    {
        if (portraitDrop.options.Count > 0)
        {
            portraitFilePath = portraitDirPath + portraitDrop.options[portraitDrop.value].text;
        }
    }
    void OnFaceTexDropChanged(int v)
    {
        if (faceTexDrop.options.Count > 0)
        {
            faceFilePath = faceDirPath + faceTexDrop.options[faceTexDrop.value].text;
        }
    }
    void OnHairTexDropChanged(int v)
    {
        if (hairTexDrop.options.Count > 0)
        {
            hairFilePath = hairDirPath + hairTexDrop.options[hairTexDrop.value].text;
            OpenHairTexture();
        }
    }
    void OnMatCapTexDropChanged(int v)
    {
        if (matCapTexDrop.options.Count > 0)
        {
            matCapFilePath = matCapDirPath + matCapTexDrop.options[matCapTexDrop.value].text;
            OpenMatCapTexture();
        }
    }
#if UNITY_EDITOR
    private void OnApplicationQuit()
    {
        // 编辑器模式时避免贴图丢失
        foreach (var mat in mats)
        {
            mat.SetTexture(baseTextureName, null);
            mat.SetTexture(darkTextureName, null);
            mat.SetTexture(dark2TextureName, null);
        }
        //smr.sharedMaterial = mats;
    }
#endif
    void DropSetPathFiles(Dropdown drop, string path)
    {
        DirectoryInfo dir = new DirectoryInfo(path);
        FileInfo[] fis = dir.GetFiles("*.*", SearchOption.TopDirectoryOnly);
        drop.options.Clear();
        foreach (var fi in fis)
        {
            drop.options.Add(new Dropdown.OptionData(fi.Name));
        }
        var c = drop.options.Count;
        if (c > 0)
        {
            var t = drop.transform;
            var itemHeight = t.Search("Item").GetComponent<RectTransform>().sizeDelta.y * c;
            var yBase = drop.GetComponent<RectTransform>().sizeDelta.y;
            var template = t.Search("Template").GetComponent<RectTransform>();
            template.sizeDelta = new Vector2(0, itemHeight);
            var content = t.Search("Content").GetComponent<RectTransform>();
            content.sizeDelta = new Vector2(0, itemHeight * 0.5f);
            if (drop.value < 0) drop.value = 0;
            drop.captionText.text = drop.options[drop.value].text;
            drop.onValueChanged.Invoke(drop.value);
        }        
        else drop.captionText.text = "无";
    }
    public void RefreshDropList()
    {
        DropSetPathFiles(portraitDrop, portraitDirPath);
        DropSetPathFiles(faceTexDrop, faceDirPath);
        DropSetPathFiles(hairTexDrop, hairDirPath);
        DropSetPathFiles(matCapTexDrop, matCapDirPath);
    }
    internal void SetDropByText(Dropdown drop, string v)
    {
        for (int i = 0; i < drop.options.Count; i++)
        {
            if (drop.options[i].text.Equals(v, StringComparison.OrdinalIgnoreCase))
            {
                drop.value = i;
            }
        }
    }    
    void SetDefaultValue(Dropdown drop, Action<int> ovc)
    {
        if (drop.options.Count > 0)
        {
            for (int i = 0; i < drop.options.Count; i++)
            {
                if (drop.options[i].text.StartsWith(defaultFileName))
                {
                    drop.value = i;
                    drop.onValueChanged.Invoke(i);
                    return;
                }
            }
            ovc(drop.value);
        }
    }
    public void Awake()
    {
        emitter = GetComponent<HairUnitEmitter>();
        var dataPath = Application.dataPath;
        var rootPath = dataPath + "/../";
        portraitDirPath = rootPath + portraitFolder;
        faceDirPath = rootPath + faceTexFolder;
        hairDirPath = rootPath + hairTexFolder;
        matCapDirPath = rootPath + matCapTexFolder;
        if (!Directory.Exists(portraitDirPath))
            Directory.CreateDirectory(portraitDirPath);
        if (!Directory.Exists(faceDirPath))
            Directory.CreateDirectory(faceDirPath);
        if (!Directory.Exists(hairDirPath))
            Directory.CreateDirectory(hairDirPath);
        if (!Directory.Exists(matCapDirPath))
            Directory.CreateDirectory(matCapDirPath);
        portraitDrop.onValueChanged.AddListener(OnPortraitDropChanged);
        faceTexDrop.onValueChanged.AddListener(OnFaceTexDropChanged);
        hairTexDrop.onValueChanged.AddListener(OnHairTexDropChanged);
        matCapTexDrop.onValueChanged.AddListener(OnMatCapTexDropChanged);
        CreateTexture();

        RefreshDropList();
        SetDefaultValue(portraitDrop, OnPortraitDropChanged);
        SetDefaultValue(faceTexDrop, OnFaceTexDropChanged);
        SetDefaultValue(hairTexDrop, OnHairTexDropChanged);
        SetDefaultValue(matCapTexDrop, OnMatCapTexDropChanged);

        paintToEyeWhiteToggle.onValueChanged.AddListener(OnPaintToEyeWhiteToggleChanged);
        brushSizeSlider.Init(brushSizeMOM, OnBrushSizeChanged);
        brushStrengthSlider.Init(brushStrengthMOM, OnBrushStrengthChanged);
        brushTypeSlider.Init(brushTypeMOM, OnBrushTypeChanged, true);
        brushColorSlider.Init(brushColor, OnBrushColorChanged);
        brushTypePreviewImage.color = brushColor;
        openTextureButton.onClick.AddListener(OpenTexture);
        openHairTextureButton.onClick.AddListener(OpenHairTexture);
        saveTextureButton.onClick.AddListener(SaveTexture);
        OpenTexture();
        OpenHairTexture();
    }
    private void OnBrushColorChanged(float v)
    {
        brushColor = brushColorSlider.color;
        brushTypePreviewImage.color = brushColor;
    }
    private void OnBrushTypeChanged(float v)
    {
        brushType = (int)v;
        brushTypePreviewImage.sprite = brushTypePreviewSprites[brushType];
    }
    void OnBrushSizeChanged(float v)
    {
        if (ignoreChange) return;
        var i = (int)(v * 100);
        v = i * 0.01f;
        var o = ignoreChange;
        ignoreChange = true;
        brushSize = v;//笔刷大小
        brushSizeSlider.value = v;
        ignoreChange = o;
    }
    void OnBrushStrengthChanged(float v)
    {
        if (ignoreChange) return;
        var i = (int)(v * 100);
        v = i * 0.01f;
        var o = ignoreChange;
        ignoreChange = true;
        brushStrength = v;//笔刷强度
        brushStrengthSlider.value = v;
        ignoreChange = o;
    }
    // 判断点是否在三角形内
    // 出处 http://www.cnblogs.com/graphics/archive/2010/08/05/1793393.html
    bool PointInTriangle(Vector3 A, Vector3 B, Vector3 C, Vector3 P)
    {
        Vector3 v0 = C - A;
        Vector3 v1 = B - A;
        Vector3 v2 = P - A;
        float dot00 = Vector3.Dot(v0, v0);
        float dot01 = Vector3.Dot(v0, v1);
        float dot02 = Vector3.Dot(v0, v2);
        float dot11 = Vector3.Dot(v1, v1);
        float dot12 = Vector3.Dot(v1, v2);
        float inverDeno = 1 / (dot00 * dot11 - dot01 * dot01);
        float u = (dot11 * dot02 - dot01 * dot12) * inverDeno;
        if (u < 0 || u > 1) // if u out of range, return directly
        {
            return false;
        }
        float v = (dot00 * dot12 - dot01 * dot02) * inverDeno;
        if (v < 0 || v > 1) // if v out of range, return directly
        {
            return false;
        }
        return u + v <= 1;
    }
    // 绘制贴图功能（原作者：喵喵Mya）
    // http://blog.csdn.net/fenglyu/article/details/71155499
    void Paint(RaycastHit hit)
    {
        float orthographicSize = (brushSize * smr.transform.localScale.x) * (smr.bounds.size.x / 0.005f);//笔刷在模型上的正交大小
        var brushSizeInPercent = (int)Mathf.Round((brushSize * faceTex.width) * 0.01f);//笔刷在模型上的大小
        //Handles.DrawWireDisc(raycastHit.point, raycastHit.normal, orthographicSize);//根据笔刷大小在鼠标位置显示一个圆

        Vector2 pixelUV = hit.textureCoord;
        var verts = smr.sharedMesh.GetTriangles(2); // eyewhite's submesh
        var uv = smr.sharedMesh.uv;
        var paintToEyeWhite = paintToEyeWhiteOn;
        for (int i = 0; i < verts.Length - 2; i += 3)
        {
            if (PointInTriangle(uv[verts[i]], uv[verts[i + 1]], uv[verts[i + 2]], pixelUV))
            {
                paintToEyeWhite = !paintToEyeWhite;
                break;
            }
        }
        if (paintToEyeWhite) return;
        //计算笔刷所覆盖的区域
        int PuX = Mathf.FloorToInt(pixelUV.x * faceTex.width);
        int PuY = Mathf.FloorToInt(pixelUV.y * faceTex.height);
        int x = Mathf.Clamp(PuX - brushSizeInPercent / 2, 0, faceTex.width - 1);
        int y = Mathf.Clamp(PuY - brushSizeInPercent / 2, 0, faceTex.height - 1);
        int width = Mathf.Clamp((PuX + brushSizeInPercent / 2), 0, faceTex.width) - x;
        int height = Mathf.Clamp((PuY + brushSizeInPercent / 2), 0, faceTex.height) - y;

        Color[] terrainBay = faceTex.GetPixels(x, y, width, height, 0);//获取Control贴图被笔刷所覆盖的区域的颜色
        Texture2D tBrush = brushTex[brushType] as Texture2D;//获取笔刷性状贴图
        float[] brushAlpha = new float[brushSizeInPercent * brushSizeInPercent];//笔刷透明度

        //根据笔刷贴图计算笔刷的透明度
        for (int i = 0; i < brushSizeInPercent; i++)
        {
            for (int j = 0; j < brushSizeInPercent; j++)
            {
                brushAlpha[j * brushSizeInPercent + i] = tBrush.GetPixelBilinear(((float)i) / brushSizeInPercent, ((float)j) / brushSizeInPercent).a;
            }
        }
        //计算绘制后的颜色
        for (int i = 0; i < height; i++)
        {
            for (int j = 0; j < width; j++)
            {
                int index = (i * width) + j;
                float strength = brushAlpha[Mathf.Clamp((y + i) - (PuY - brushSizeInPercent / 2), 0, brushSizeInPercent - 1) * brushSizeInPercent + Mathf.Clamp((x + j) - (PuX - brushSizeInPercent / 2), 0, brushSizeInPercent - 1)] * brushStrength;

                terrainBay[index] = Color.Lerp(terrainBay[index], toggle.isOn ? Color.white : brushColor, strength);
            }
        }
        faceTex.SetPixels(x, y, width, height, terrainBay, 0);//把绘制后的Control贴图保存起来
        faceTex.Apply();
    }    
    void Update()
    {
        if (!facePanelToggle.isOn ||  !isOn || !Input.GetMouseButton(0)) return;
        var mask = 1 << LayerMask.NameToLayer("Paintable");
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        bool bHit = Physics.Raycast(ray, out hit, Mathf.Infinity, mask, QueryTriggerInteraction.Collide);
        if (bHit)
        {
            Paint(hit);
        }
    }
    void CreateTexture()
    //创建贴图
    {
        //创建一个新的Contol贴图
        Color[] colorBase = new Color[textureSqrtSize * textureSqrtSize];
        for (int t = 0; t < colorBase.Length; t++)
        {
            colorBase[t] = Color.white;
        }
        faceTex = new Texture2D(textureSqrtSize, textureSqrtSize, TextureFormat.ARGB32, true);
        faceTex.SetPixels(colorBase);
        faceTex.Apply();
        hairTex = new Texture2D(textureSqrtSize, textureSqrtSize, TextureFormat.ARGB32, true);
        hairTex.SetPixels(colorBase);
        hairTex.Apply();
        matCapTex = new Texture2D(textureSqrtSize, textureSqrtSize, TextureFormat.ARGB32, true);
        matCapTex.SetPixels(colorBase);
        matCapTex.Apply();
        for (int i = skin; i < mats.Count; i++)
        {
            mats[i].SetTexture(baseTextureName, faceTex);
            mats[i].SetTexture(darkTextureName, faceTex);
            mats[i].SetTexture(dark2TextureName, faceTex);
        }
    }
    public void OpenMatCapTexture()
    {
        try
        {
            if (!File.Exists(matCapFilePath)) return;
            var bytes = File.ReadAllBytes(matCapFilePath);
            matCapTex.LoadImage(bytes);
            foreach (var unit in emitter.hairUnits)
            {
                var mat = unit.smr.sharedMaterial;
                mat.SetTexture(matCapTextureName, matCapTex);
            }
            mats[hair0].SetTexture(matCapTextureName, matCapTex);

        }
        catch { throw; }
    }
    public void OpenHairTexture()
    {
        try
        {
            if (!File.Exists(hairFilePath)) return;
            var bytes = File.ReadAllBytes(hairFilePath);
            hairTex.LoadImage(bytes);
            foreach (var unit in emitter.hairUnits)
            {
                var mat = unit.smr.sharedMaterial;
                mat.SetTexture(baseTextureName, hairTex);
                mat.SetTexture(darkTextureName, hairTex);
                mat.SetTexture(dark2TextureName, hairTex);
            }
            mats[hair0].SetTexture(baseTextureName, hairTex);
            mats[hair0].SetTexture(darkTextureName, hairTex);
            mats[hair0].SetTexture(dark2TextureName, hairTex);
        }
        catch { throw; }
    }
    public void OpenTexture()
    {
        try
        {
            if (!File.Exists(faceFilePath)) return;
            var bytes = File.ReadAllBytes(faceFilePath);
            faceTex.LoadImage(bytes);
            for (int i = skin; i < mats.Count; i++)
            {
                mats[i].SetTexture(baseTextureName, faceTex);
                mats[i].SetTexture(darkTextureName, faceTex);
                mats[i].SetTexture(dark2TextureName, faceTex);
            }
        }
        catch { throw; }
    }
    public void SaveTexture()
    {
        try
        {
            faceTex = mats[skin].GetTexture(baseTextureName) as Texture2D;
            var bytes = faceTex.EncodeToPNG();
            File.WriteAllBytes(faceFilePath, bytes);
        }
        catch { throw; }
    }
}
